#ifndef MINHA_MATRIZ_FACADE_H_
#define MINHA_MATRIZ_FACADE_H_

/*
TRABALHO 1 de COMPUTACAO 3 2007/2
Alunos:
 Anderson Marinho (DRE: 104042916)
 Artur Souza (DRE: 104020972)
*/

#include "minha_matriz.h"
#include "minha_matriz_esparsa.h"

template <class T> class minha_matriz_facade;

template<class T> class minha_matriz;

template<class T> class minha_matriz_facade_linha {
private:
	int l;
	minha_matriz_facade<T> * m;
public:
	minha_matriz_facade_linha(int _l, minha_matriz_facade<T> * _m) :
		l(_l), m(_m) {
	}
	T& operator[](int c) const {
		return (*m)(l, c);
	}
	int getLin() const {
		return l;
	}
	int getCol() const {
		return (*m).getCol();
	}
	friend ostream& operator <<(ostream& o, const minha_matriz_facade_linha<T>& mml) {
		for (int j = 0; j < mml.getCol(); j++) {
			o << (j>0 ? " " : "")<< "("<< mml.getLin() << ","<< j << ")="
					<< mml[j];
		}
		return o;
	}
};

template<class T> class minha_matriz_facade_coluna {
private:
	int c;
	minha_matriz_facade<T> * m;
public:
	minha_matriz_facade_coluna(int _c, minha_matriz_facade<T> * _m) :
		c(_c), m(_m) {
	}
	T& operator()(int l) const {
		return (*m)(l, c);
	}
	T& operator[](int l) const {
		return (*m)(l, c);
	}
	int getLin() const {
		return (*m).getLin();
	}
	int getCol() const {
		return c;
	}
	friend ostream& operator <<(ostream& o, const minha_matriz_facade_coluna<T>& mmc) {
		for (int j = 0; j < mmc.getLin(); j++) {
			o << (j>0 ? " " : "")<< "("<< j << ","<< mmc.getCol() << ")="
					<< mmc(j);
		}
		return o;
	}
};

template <class T>
class minha_matriz_facade  {
private:
	char tipo; //0 - esparsa, 1 - cheia
	minha_matriz<T> * mm;
	minha_matriz_esparsa<int,T> * mme;

	void full() {
		tipo = 1;
		mm = new minha_matriz<T>(mme->getLin(),mme->getCol());
		for(int i = 0; i < mm->getLin(); i++) {
			for(int j = 0; j < mm->getCol(); j++) {
				(*mm)(i,j) = (*mme)(i,j);
			}
		}
		delete mme;
	}

	void free() {
		if (tipo)
			delete mm;
		else
			delete mme;
		mm = NULL;
		mme = NULL;
	}

public:

	minha_matriz_facade(int n):tipo(0),mm(NULL),mme(new minha_matriz_esparsa<int,T>(n)) {
	}

	minha_matriz_facade(int lin, int col):tipo(0),mm(NULL),mme(new minha_matriz_esparsa<int,T>(lin,col)) {
	}

	minha_matriz_facade(const minha_matriz<T>& _mm):tipo(1),mm(new minha_matriz<int>(_mm)),mme(NULL) {
	}

	minha_matriz_facade(const minha_matriz_facade<T>& m):tipo(m.tipo),mm(NULL),mme(NULL) {
		if (tipo) {
			mm = new minha_matriz<T>(*(m.mm));
		} else {
			mme = new minha_matriz_esparsa<int,T>(*(m.mme));
		}
	}

	virtual ~minha_matriz_facade() {
		free();
	}

	int getLin() const{
		return tipo?mm->getLin():mme->getLin();
	}

	int getCol() const {
		return tipo?mm->getCol():mme->getCol();
	}

	T& operator ()(const int i, const int j) {
		if (tipo) {
			return (*mm)(i,j);
		} else {
			T& ret = (*mme)(i,j);
			if (mme->utilizacao() > 0.2) {
				full();
				return (*mm)(i,j);
			}
			return ret;
		}
	}

	friend ostream& operator <<(ostream& o, minha_matriz_facade<T>& mm) {
		if (mm.tipo)
			o << *mm.mm;
		else
			o << *mm.mme;
		return o;
	}


	minha_matriz_facade<T>& operator =(const minha_matriz_facade<T>& mm2) {
		free();
		tipo = mm2.tipo;
		if (tipo) {
			mm = new minha_matriz<T>(*mm2.mm);
		} else {
			mme = new minha_matriz_esparsa<int,T>(*mm2.mme);
		}
		return *this;
	}

	minha_matriz_facade<T>& operator =(const minha_matriz<T>& mm2) {
		free();
		tipo = 1;
		mm = new minha_matriz<T>(mm2);
		return *this;
	}

	minha_matriz_facade<T>& operator =(const minha_matriz_esparsa<int,T>& mm2) {
		free();
		tipo = 0;
		mme = new minha_matriz_esparsa<int,T>(mm2);
		full();
		return *this;
	}

	friend minha_matriz_facade<T> operator +(const minha_matriz_facade<T>& mm1,
			const minha_matriz_facade<T>& mm2) {
		minha_matriz_facade<T> m1 = mm1;
		if (!m1.tipo) {
			m1.full();
		}
		minha_matriz_facade<T> m2 = mm2;
		if (!m2.tipo) {
			m2.full();
		}
		return minha_matriz_facade<T>(*(m1.mm) + *(m2.mm));
	}

	friend minha_matriz_facade<T> operator *(const minha_matriz_facade<T>& mm1,
			const minha_matriz_facade<T>& mm2) {
		minha_matriz_facade<T> m1 = mm1;
		if (!m1.tipo) {
			m1.full();
		}
		minha_matriz_facade<T> m2 = mm2;
		if (!m2.tipo) {
			m2.full();
		}
		return minha_matriz_facade<T>(*(m1.mm) * *(m2.mm));
	}

	minha_matriz_facade_coluna<T> operator ()(int c) {
		return minha_matriz_facade_coluna<T>(c, this);
	}

	minha_matriz_facade_linha<T> operator [](int l) {
		return minha_matriz_facade_linha<T>(l, this);
	}


	class Iterator : public std::iterator<std::forward_iterator_tag, T> {
public:
		Iterator(minha_matriz_facade<T>* _m, int lin=0, int col=0) :
			itM(_m), itNLin(lin), itNCol(col) {
		}
		~Iterator() {
		}

		bool operator!=(const Iterator& other) {
			return (itNLin != other.itNLin);
		}

		Iterator& operator++() {
			itNCol = (itNCol+1)%itM->getCol();
			if (!itNCol)
				itNLin++;
			return (*this);
		}

		T operator*() {
			return (*itM)(itNLin, itNCol);
		}

private:
		minha_matriz_facade<T>* itM;
		int itNLin, itNCol;
	};

	class IteradorLinha : public std::iterator<std::forward_iterator_tag, T> {
public:
		IteradorLinha(minha_matriz_facade<T>* _m, int lin=0) :
			itM(_m), itNLin(lin) {
		}
		~IteradorLinha() {
		}

		bool operator!=(const IteradorLinha& other) {
			return (itNLin != other.itNLin);
		}

		IteradorLinha& operator++() {
			itNLin++;
			return (*this);
		}

	    minha_matriz_facade_linha<T> operator*() {
			return (*itM)[itNLin];
		}

private:
		minha_matriz_facade<T>* itM;
		int itNLin;
	};

	class IteradorColuna : public std::iterator<std::forward_iterator_tag, T> {
public:
		IteradorColuna(minha_matriz_facade<T>* _m, int col=0) :
			itM(_m), itNCol(col) {
		}
		~IteradorColuna() {
		}

		bool operator!=(const IteradorColuna& other) {
			return (itNCol != other.itNCol);
		}

		IteradorColuna& operator++() {
			itNCol++;
			return (*this);
		}

		minha_matriz_facade_coluna<T> operator*() {
			return (*itM)(itNCol);
		}

private:
		minha_matriz_facade<T>* itM;
		int itNCol;
	};

	Iterator begin() {
		return (Iterator(this));
	}

	Iterator end() {
		return (Iterator(this, this->getLin(), this->getCol()));
	}

	IteradorLinha beginLinha() {
		return (IteradorLinha(this));
	}

	IteradorLinha endLinha() {
		return (IteradorLinha(this, this->getLin()));
	}

	IteradorColuna beginColuna() {
		return (IteradorColuna(this));
	}

	IteradorColuna endColuna() {
		return (IteradorColuna(this, this->getCol()));
	}

};

#endif /*MINHA_MATRIZ_FACADE_H_*/
